10.6 变量生存期和变量作用域

作用域与生存期

  • 作用域:一个变量可以被引用的范围,包括全局作用域、文件作用域和条件作用域

  • 生命周期:变量可以被引用的时间段,一个程序的内存分为代码区、全局数据区、堆区和栈区,不同的内存区域对应不同的生命周期

内存区域

一个程序将操作系统分配给其运行的内存块分为四块区域:

  • 代码区:存放程序的代码

  • 全局数据区:存放程序的全局数据和静态数据

  • 堆区:存放程序的动态数据

  • 栈区:存放程序的局部数据,即各个函数中的数据

变量分类:作用域和生存期

从变量作用域(全局作用域、文件作用域和局部作用域)来分,可以分为:

  • 全局变量:函数外部定义,作用域是整个源程序(静态全局变量的作用域是该文件范围,即文件作用域)

  • 局部变量:函数内部定义,作用域仅限于函数内

从变量生存期(只和变量存储的位置相关)角度来分,可以分为:

  • 静态存储方式:在程序运行期间,系统对变量分配固定的存储空间

  • 动态存储方式:在程序运行期间,系统对变量分配动态(不固定)的存储空间

变量的存储类别

生存周期仅仅和变量存储的位置相关,变量的存储类别可以分为静态存储方式动态存储方式

变量存储类型说明包括:

  • auto:自动变量,动态存储方式

  • static:静态变量,静态存储方式

  • register:寄存器变量,动态存储方式

  • extern:外部变量,静态存储方式

四种类型说明符

1. 自动变量的类型说明符auto

C语言规定,函数内凡是未加存储类型说明的变量均视为自动变量,即自动变量可省去auto说明符。例如:

int i, j;
char c;

// 等价于
auto int i, j;
auto char c;
  • 自动变量的作用域仅限于定义该变量的个体内,比如函数中定义的自动变量仅在函数内有效,复合语句(==比如if后的复合语句中定义的变量==)中定义的自动变量仅在该符合语句中有效

  • 自动变量属于动态存储方式,只有在定义该变量的函数被调用时才给它分配存储单元,开始它的生存期,函数调用结束后释放存储单元结束生存期。因此函数调用结束后,自动变量的值不能保留。

2. 外部变量的说明符extern

  • 外部变量和全局变量是对同一类变量的两种角度的提法,全局变量是从它的作用域提出的,而外部变量是从它的存储方式提出的,表示了它的生存期。

  • ==当一个源程序由若干个源文件组成时,在一个源文件中定义的外部变量在其他的源文件中也有效==

举个例子:

foo1.cfoo2.c中都需要使用a,b,c三个变量,在foo1.c中把这三个变量都定义为外部变量,在foo2.c中再用extern把三个变量说明为外部变量,表示这些变量已经在其他文件中定义,编译器不再为这些变量分配内存空间。

// foo1.c
int a, b;  /* 外部变量定义 */
char c;   /* 外部变量定义 */
int main(void)
{
    //...
}

// foo2.c
extern int a, b;   /* 外部变量说明 */
extern char c;    /* 外部变量说明 */
int func (void)
{
    //...
}

静态变量static

静态变量的类型说明符是static,属于静态存储方式,但是静态存储方式的变量不一定就是静态变量,例如外部变量属于静态存储方式但不一定是静态变量,必须由static加以定义后才能成为静态外部变量,或称静态全局变量。对于自动变量,前面已经介绍它属于动态存储方式,但是也可以用static定义它为静态自动变量,或称静态局部变量,从而成为静态存储方式。

1. 静态局部变量

在局部变量前面加上static说明符就构成静态局部变量

例如:

static int a;
static floatarray[5]={1,2,3,4,5};
  • 静态局部变量在函数内定义,但不像自动变量那样当函数被调用时就存在,调用结束就消失,静态变量始终存在着,也就是它的生存期为整个源程序

  • 静态变量的生存期虽然为整个源程序,但是作用域与自动变量相同,即只能在定义该变量的函数内使用该变量,退出函数后虽然变量还存在,但不能够使用它

  • 允许对构造类静态局部量赋初始值

  • 对基本类型的静态局部变量如果在声明时未赋初始值,则系统自动赋0值,==而对自动变量不赋初始值,那么它的值是不确定的==。

根据静态局部变量的特点,它的生存期为整个源程序,==在离开定义它的函数(作用域)但再次调用定义它的函数时,它又可继续使用,而且保存了前次被调用后留下的值。==因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量,虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此最好采用局部静态变量。

例子:

int main(void)
{
    int i;
    void function(); /* 函数声明 */
    for (i=1; i<=5; i++)
    {
        function(); /* 函数调用 */
    }
}

void f() /* 函数定义 */
{
    auto int j=0;
    
    Static int k=0;
    ++j;
    ++k;
    printf("%d %d/n", j , k);
}

程序中定义了函数f,其中的变量j说明为自动变量并赋予初始值为0。当main中多次调用f时,j均赋值为0,故每次输出值都是1。而kstatic型变量,每次保留之前值所以依次为1,2,3,4,5。

2. 静态全局变量

全局变量(外部变量)的说明之前加上static就构成了静态的全局变量,==全局变量本身就是静态存储变量,静态全局变量当然也是静态存储方式==。这两者在存储方式上并无不同,这两者的区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源程序组成时,非静态的全局变量在各个源文件中都是有效的,而==静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效==,在同一源程序的其他源文件中不能使用它。

由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数使用,因此可以避免在其他源文件中不能使用它。

3. 总结

static这个说明符在不同地方所起的作用域是不同的,比如把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期,把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。

寄存器变量register

变量一般都存放在存储器内,因此对一个变量频繁读写时,必须要反复访问内存储器,从而花费大量的存取时间。

为此,C语言提供了一种寄存器变量。这种变量存放在CPU的寄存器中,使用时不需求访问内存,而直接从寄存器中读写,这样可提高效率。寄存器变量的说明符是register,对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量。

register i, s = 0;
for (i = 1; i <= 200; i++)
s = s + i;
printf("s=%d/n", s)

在本程序循环200次,is都将频繁使用,因此可定义为寄存器变量,对寄存器变量需要说明以下几点:

  • 只有局部自动变量和形式参数才可以定义为寄存器变量,因为寄存器变量属于动态存储方式。凡需要采用静态存储方式的量不能定义为存储器变量。

  • 在Turbo C, MS C等微机上使用的C语言中,实际上是把寄存器变量当成自动变量处理的。因此速度并不能提高,而在程序中允许使用寄存器变量只是为了与标准C保持一致

  • 即使能真正使用寄存器变量的机器,由于CPU中寄存器的个数是有限的,因此使用寄存器变量的个数都是有限的

四种变量的作用域、生命周期和内存分布

1. 全局变量

  • 作用域:全局作用域(全局变量只需要在一个源文件中定义,就可以作用于所有的源文件)

  • 生命周期:程序运行期一直存在

  • 饮用方法:其他文件中要使用必须用extern关键字声明要引用的全局变量

  • 内存分布:全局数据区

  • 注意:如果在两个文件中都定义了相同名字的全局变量,会出现链接出错:变量重定义

  • 例子:

// define.cpp
int varFoo = 1;

// main.cpp
extern int varFoo;

int main()
{
    cout << varFoo;
    return 0;
}

2. 全局静态变量

  • 作用域:文件作用域(只在被定义的文件中可见)

  • 生命周期:程序运行期间一直存在

  • 内存分布:全局数据区

  • 定义方法: static关键字和const关键字

  • 注意:只要文件不相互包含,在两个不同的文件中是可以定义完全相同的两个静态变量的,它们是两个完全不同的变量

  • 例子:

const int varFoo1;
static const int varFoo2;
static int varFoo3;

int main(void)
{
    return 0;
}

3. 静态局部变量

  • 作用域:局部作用域(只在局部作用域可见)

  • 生命周期:程序运行期一直存在

  • 内存分布:全局数据区

  • 定义方法:局部作用域中用static定义

  • 注意:只被初始化一次,多线程中需加锁保护

  • 例子:

void fooFunction()
{
    static int varFoo = 0;
}

4. 局部变量

  • 作用域:局部作用域(只在局部作用域内可见)

  • 生命周期:程序运行出局部作用域既被销毁

  • 内存分布:栈区

  • 注意:使用auto说明符标识

Reference

[1] https://blog.csdn.net/u012679707/article/details/80188124